#include "pcl.hpp"

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include "../log.hpp"
#include "../debug.hpp"
#include "pcl_config.h"

#if defined(CO_USE_FIBERS)
#define _WIN32_WINNT 0x0400
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef _MSC_VER
#pragma comment(lib, "kernel32.lib")
#endif

#elif defined(CO_USE_UCONEXT)
#include <ucontext.h>
#else
#include <setjmp.h>
#endif

#if defined(CO_USE_SIGCONTEXT)
#include <signal.h>
#endif

namespace pcl
{

typedef size_t integerptr_t;

#if defined(CO_USE_FIBERS)
typedef void* ctx_t;
#elif defined(CO_USE_UCONEXT)
typedef ucontext_t ctx_t;
#else
typedef jmp_buf ctx_t;
#endif

struct coro_impl
{
	ctx_t ctx;
#if !defined(CO_USE_FIBERS)
	char* stack;
#else
	coro_impl(ctx_t ctx)
	: ctx(ctx)
	{
	}
#endif // #if !defined(CO_USE_FIBERS)

	~coro_impl()
	{
#	if defined(CO_USE_FIBERS)
		if(ctx != GetCurrentFiber())
			DeleteFiber(ctx);
#	else
		free(stack);
#	endif
	}
};

namespace
{

struct coro_is_done // Exception that is thrown when a coroutine that is flagged as done is resumed
{};

std::size_t const min_stack_size = 1024 * 4;

//coro main((coro::main_tag()));
coro* main = new coro((coro::main_tag()));
coro* curr = main;
coro* dhelper = 0;
#ifdef ENABLE_AUTO_SCHEDULER
scheduler* curr_scheduler = 0;
#endif

#if defined(CO_USE_SIGCONTEXT)

volatile int ctx_called;
ctx_t* ctx_creating;
void* ctx_creating_func;
sigset_t ctx_creating_sigs;
ctx_t ctx_trampoline;
ctx_t ctx_caller;

#endif /* #if defined(CO_USE_SIGCONTEXT) */


int ctx_sdir(integerptr_t psp)
{
	int nav = 0;
	integerptr_t csp = integerptr_t(&nav);

	return psp > csp ? -1: +1;
}


static int ctx_stackdir(void)
{
	int cav = 0;

	return ctx_sdir(integerptr_t(&cav));
}

#if defined(CO_USE_FIBERS)

void switch_context(ctx_t& octx, ctx_t& nctx)
{
	if(!main->impl)
	{
		main->impl = new coro_impl(ConvertThreadToFiber(main));
	}
	else if(PVOID(0x1e00) == GetCurrentFiber())
	{
		// Somehow we're not in a fiber, convert to a fiber so we can switch
		main->impl->ctx = ConvertThreadToFiber(main);
	}
	
	SwitchToFiber(nctx);
}
#endif


#if defined(CO_USE_FIBERS)

static VOID CALLBACK fiber_runner(PVOID lpParameter)
{
	coro* co = curr;
	
	co->restarget = co->caller;
	
	if(!co->is_done()) // Check if the coroutine was already flagged as done
	{
		try
		{
			co->go();
		}
		catch(std::exception& e)
		{
			ILOG("Coro exited from exception: " << e.what());
		}
		catch(...)
		{
			
		}
		
		co->flags |= coro::done;
	}
	
	
	// Wait for the destruction
	for(;;)
	{
		try
		{
			resume();
		}
		catch(...)
		{
		}
	}
	
}
#endif

struct del_helper : coro
{
	del_helper()
	: coro(min_stack_size)
	{
	}
	
	void go()
	{
		for(;;)
		{
			coro* cdh = dhelper;
			dhelper = 0;
			delete curr->caller;
			cdh->call();
			
			if(!dhelper)
			{
				throw error("Resume to delete helper coroutine");
			}
		}
	}
};

#ifdef ENABLE_AUTO_SCHEDULER
void delete_scheduler()
{
	delete curr_scheduler;
}
#endif

} // namespace

coro::coro(std::size_t stack_size)
: flags(0)
{
#if defined(CO_USE_FIBERS)
	if(stack_size < min_stack_size)
	{
		stack_size = min_stack_size;
	}
	
	void* fiber = CreateFiber(stack_size, fiber_runner, this);
	/* TODO: Throw on error
	if (!co->ctx.cc) {
		return NULL;
	}*/
	impl = new coro_impl(fiber);
#endif
}

void coro::clean_up()
{
	if(!(flags & done))
	{
		flags |= done;
		call();
	}
}

coro::~coro()
{
	if(this == main)
		throw error("Attempt to delete main coro");
	
	if(this != curr) // It's not safe to clean up itself
	{
		clean_up();
	}
	
	delete impl;
}


void coro::call()
{
	coro* oldco = curr;
	
	ASSERT(oldco != this);
	
	caller = curr;
	curr = this;

	switch_context(oldco->impl->ctx, impl->ctx);

	if(curr->is_done())
		throw coro_is_done();
}


void resume()
{
#ifdef ENABLE_AUTO_SCHEDULER
	if(curr_scheduler)
	{
		if(curr->is_done())
			curr_scheduler
		coro* n = curr_scheduler->next(curr);
		if(n != curr)
			n->call();
	}
#endif
	
	curr->restarget->call();
	curr->restarget = curr->caller;
}

#ifdef ENABLE_AUTO_SCHEDULER
void yield()
{
	
}

void run_scheduler(scheduler* new_scheduler)
{
	delete curr_scheduler;
	if(!curr_scheduler)
		std::at_exit(delete_scheduler);
		
	curr_scheduler = new_scheduler;
	curr_scheduler->org_coro = curr;
}
#endif

void coro::exit_to()
{
	static coro* dchelper = 0;

	if(!dchelper)
	{
		dchelper = new del_helper();
	}

	dhelper = this;
 
	dchelper->call();
	
	throw error("Stale coroutine called");
}

void exit()
{
	curr->restarget->exit_to();
}

} // namespace pcl
